<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <!-- hexo-inject:begin --><!-- hexo-inject:end --><meta charset="utf-8">
    <meta name="keywords" content="Vue源码系列2-数据响应式原理, HTML,CSS,JavaScript,JQuery,React,Vue.js等">
    <meta name="description" content="别做一个Vue api调用师">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <!-- Global site tag (gtag.js) - Google Analytics -->


    <title>Vue源码系列2-数据响应式原理 | zihao blog</title>
    <link rel="icon" type="image/png" href="/pride-blog/favicon.png">

    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/pride-blog/css/my.css">

    <script src="/pride-blog/libs/jquery/jquery.min.js"></script>

<meta name="generator" content="Hexo 5.3.0"><link rel="stylesheet" href="/pride-blog/css/prism-tomorrow.css" type="text/css"><!-- hexo-inject:begin --><!-- hexo-inject:end --></head>




<body>
    <!-- hexo-inject:begin --><!-- hexo-inject:end --><header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/pride-blog/" class="waves-effect waves-light">
                    
                    <img src="/pride-blog/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">zihao blog</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/pride-blog/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/pride-blog/categories/CSS/" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/pride-blog/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="/pride-blog/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">zihao blog</div>
        <div class="logo-desc">
            
            别做一个Vue api调用师
            
        </div>
    </div>

    

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/pride-blog/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/pride-blog/categories/CSS/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/pride-blog/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        
    </ul>
</div>


        </div>

        
    </nav>

</header>

    



<div class="bg-cover pd-header post-cover" style="background-image: url('/pride-blog/medias/featureimages/13.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 class="description center-align post-title">Vue源码系列2-数据响应式原理</h1>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/pride-blog/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        padding: 35px 0 15px 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        padding-bottom: 30px;
        overflow: auto;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/pride-blog/tags/Vue%E6%BA%90%E7%A0%81/">
                                <span class="chip bg-color">Vue源码</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/pride-blog/categories/Vue%E6%BA%90%E7%A0%81/" class="post-category">
                                Vue源码
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2021-06-10
                </div>
                

                

                

                

                
            </div>
        </div>
        <hr class="clearfix">

        

        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <h3 id="Vue-源码系列-2"><a href="#Vue-源码系列-2" class="headerlink" title="Vue 源码系列 2"></a>Vue 源码系列 2</h3><ul>
<li>带着问题去思考:<ul>
<li><code>Vue数据响应式的原理？</code></li>
<li><code>computed 、 watch 、 methods三者之间有什么区别？</code></li>
</ul>
</li>
</ul>
<h4 id="先找到初始化-State-的方法-initState-函数"><a href="#先找到初始化-State-的方法-initState-函数" class="headerlink" title="先找到初始化 State 的方法 initState 函数"></a>先找到初始化 State 的方法 initState 函数</h4><p><code>目录:/src/core/instance/state.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">/**
 * 两件事：
 *   数据响应式的入口：分别处理 props、methods、data、computed、watch
 *   优先级：props、methods、data、computed 对象中的属性不能出现重复，优先级和列出顺序一致
 *         其中 computed 中的 key 不能和 props、data 中的 key 重复，methods 不影响
 */
export function initState(vm: Component) &#123;
    vm._watchers = []
    const opts = vm.$options
    // 处理 props 对象，为 props 对象的每个属性设置响应式，并将其代理到 vm 实例上
    if (opts.props) initProps(vm, opts.props)
    // 处理 methos 对象，校验每个属性的值是否为函数、和 props 属性比对进行判重处理，最后得到 vm[key] = methods[key]
    if (opts.methods) initMethods(vm, opts.methods)
    /**
     * 做了三件事
     *   1、判重处理，data 对象上的属性不能和 props、methods 对象上的属性相同
     *   2、代理 data 对象上的属性到 vm 实例
     *   3、为 data 对象的上数据设置响应式
     */
    if (opts.data) &#123;
        initData(vm)
    &#125; else &#123;
        observe(vm._data = &#123;&#125;, true /* asRootData */ )
    &#125;
    /**
     * 三件事：
     *   1、为 computed[key] 创建 watcher 实例，默认是懒执行
     *   2、代理 computed[key] 到 vm 实例
     *   3、判重，computed 中的 key 不能和 data、props 中的属性重复
     */
    if (opts.computed) initComputed(vm, opts.computed)
    /**
     * 三件事：
     *   1、处理 watch 对象
     *   2、为 每个 watch.key 创建 watcher 实例，key 和 watcher 实例可能是 一对多 的关系
     *   3、如果设置了 immediate，则立即执行 回调函数
     */
    if (opts.watch && opts.watch !== nativeWatch) &#123;
        initWatch(vm, opts.watch)
    &#125;

    /**
     * 其实到这里也能看出，computed 和 watch 在本质是没有区别的，都是通过 watcher 去实现的响应式
     * 非要说有区别，那也只是在使用方式上的区别，简单来说：
     *   1、watch：适用于当数据变化时执行异步或者开销较大的操作时使用，即需要长时间等待的操作可以放在 watch 中
     *   2、computed：其中可以使用异步方法，但是没有任何意义。所以 computed 更适合做一些同步计算
     */
&#125;</code></pre>
<h4 id="initProps-函数"><a href="#initProps-函数" class="headerlink" title="initProps 函数"></a>initProps 函数</h4><p><code>目录:src/core/instance/state.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">function initProps(vm: Component, propsOptions: Object) &#123;
    const propsData = vm.$options.propsData || &#123;&#125;
    const props = vm._props = &#123;&#125;
    // cache prop keys so that future props updates can iterate using Array
    // instead of dynamic object key enumeration.
    // 缓存props对象中的key;性能优化
    const keys = vm.$options._propKeys = []
    const isRoot = !vm.$parent
    // root instance props should be converted
    if (!isRoot) &#123;
        toggleObserving(false)
    &#125;
    // 遍历props对象上的key
    for (const key in propsOptions) &#123;
        // 将keys加入到缓存中
        keys.push(key)
        const value = validateProp(key, propsOptions, propsData, vm)
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') &#123;
            const hyphenatedKey = hyphenate(key)
            if (isReservedAttribute(hyphenatedKey) ||
                config.isReservedAttr(hyphenatedKey)) &#123;
                warn(
                    `"$&#123;hyphenatedKey&#125;" is a reserved attribute and cannot be used as component prop.`,
                    vm
                )
            &#125;
            // 给props对象上的key做响应式数据处理
            defineReactive(props, key, value, () => &#123;
                if (!isRoot && !isUpdatingChildComponent) &#123;
                    warn(
                        `Avoid mutating a prop directly since the value will be ` +
                        `overwritten whenever the parent component re-renders. ` +
                        `Instead, use a data or computed property based on the prop's ` +
                        `value. Prop being mutated: "$&#123;key&#125;"`,
                        vm
                    )
                &#125;
            &#125;)
        &#125; else &#123;
            defineReactive(props, key, value)
        &#125;
        // static props are already proxied on the component's prototype
        // during Vue.extend(). We only need to proxy props defined at
        // instantiation here.
        if (!(key in vm)) &#123;
            // 将Props[key]代理到Vue实例上,支持通过this.propsKey的形式访问;
            proxy(vm, `_props`, key)
        &#125;
    &#125;
    toggleObserving(true)
&#125;</code></pre>
<p><strong>initProps 函数做了什么？</strong></p>
<ul>
<li><ol>
<li>缓存了 props 对象中的 key, 做了性能优化;</li>
</ol>
</li>
<li><ol start="2">
<li>遍历 props 对象上的 key;</li>
</ol>
</li>
<li><ol start="3">
<li>通过 defineReactive 方法给 props[key]做响应式处理;</li>
</ol>
</li>
<li><ol start="4">
<li>通过 proxy 方法将 Props[key]代理到 Vue 实例上, 支持通过 this.propsKey 的形式访问;</li>
</ol>
</li>
</ul>
<h4 id="defineReactive-函数"><a href="#defineReactive-函数" class="headerlink" title="defineReactive 函数"></a>defineReactive 函数</h4><p><code>目录:/src/core/observer/index.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">export function defineReactive(
    obj: Object,
    key: string,
    val: any,
    customSetter ? : ? Function,
    shallow ? : boolean
) &#123;
    // 实例化一个Dep,一一对应的关系:一个Key一个Dep;
    const dep = new Dep()

    // MDN上定义Object.getOwnPropertyDescriptor:
    //方法为返回指定对象上一个自有属性的属性描述符
    // （自有属性是指对对象的私有属性,不在原型链上进行查找）
    // 获取对象上的Key ====> property
    const property = Object.getOwnPropertyDescriptor(obj, key)
    // MDN定义configurable：
    // 当切仅当指定对象的属性描述可以被改变或者属性可被删除时,为true
    // 发现属性描述符的configurable不可配置,直接return
    if (property && property.configurable === false) &#123;
        return
    &#125;

    // cater for pre-defined getter/setters
    // 获取属性描述符上访问器函数getter和设置气函数setter
    const getter = property && property.get
    const setter = property && property.set
    if ((!getter || setter) && arguments.length === 2) &#123;
        val = obj[key]
    &#125;

    let childOb = !shallow && observe(val)
    // MDN描述Object.defineProperty:
    // 会直接在一个对象上定义新的属性,或者修改一个对象的现有属性,并返回该对象
    // 响应式数据处理的核心
    Object.defineProperty(obj, key, &#123;
        enumerable: true,
        configurable: true,
        // 设置get方法拦截obj[key]的访问
        get: function reactiveGetter() &#123;
            const value = getter ? getter.call(obj) : val
            // Dep.target是一个watcher
            if (Dep.target) &#123;
                // 依赖收集
                dep.depend()
                if (childOb) &#123;
                    // obj[key]如果有子对象,循环调用依赖收集
                    childOb.dep.depend()
                    // 处理数组的依赖收集
                    if (Array.isArray(value)) &#123;
                        dependArray(value)
                    &#125;
                &#125;
            &#125;
            return value
        &#125;,
        // 拦截对obj[key]读取操作
        set: function reactiveSetter(newVal) &#123;
            // 通过访问器函数获取——旧值
            const value = getter ? getter.call(obj) : val
            /* eslint-disable no-self-compare */
            // 如果新值和旧值相等,直接return
            if (newVal === value || (newVal !== newVal && value !== value)) &#123;
                return
            &#125;
            /* eslint-enable no-self-compare */
            if (process.env.NODE_ENV !== 'production' && customSetter) &#123;
                customSetter()
            &#125;
            // #7981: for accessor properties without setter
            if (getter && !setter) return
            // 通过设置器函数设置新值
            if (setter) &#123;
                setter.call(obj, newVal)
            &#125; else &#123;
                val = newVal
            &#125;
            // observe为观察,让新值也是响应式的
            childOb = !shallow && observe(newVal)
            // 依赖通知更新
            dep.notify()
        &#125;
    &#125;)
&#125;</code></pre>
<p><strong>defineReactive 函数做了什么？</strong></p>
<ul>
<li>1.0 实例化一个 Dep, 一个 key 对应一个 Dep, 是一一对应的关系;</li>
<li>2.0 通过 obejct.getOwnPropertydescriptor 方法获取 obj[key]上的属性描述符;</li>
<li>3.0 缓存每一个 key 上的 getter(访问器函数)和 setter(设置器函数);</li>
<li>4.0 通过 Object.defineProperty 方法给每一个 key 设置一个 get 方法和 set 方法;</li>
<li>5.0 get 方法用于拦截对 obj[key]的访问, 并对 key 进行依赖收集;</li>
<li>6.0 set 方法用于拦截对 obj[key]的设置, 对 key 进行 value 的更新; 如果 value 更新了调用 Dep.notify 方法通知 watcher 更新;</li>
</ul>
<h4 id="总结-initProps-函数做了那些事情？"><a href="#总结-initProps-函数做了那些事情？" class="headerlink" title="总结 initProps 函数做了那些事情？"></a>总结 initProps 函数做了那些事情？</h4><ul>
<li><ol>
<li>缓存 props 对象上的 key, 做性能优化</li>
</ol>
</li>
<li><ol start="2">
<li>通过 defineReactive 方法给 props 对象上的 key 做响应式数据处理</li>
</ol>
<ul>
<li>2.1 实例化一个 Dep, 一个 key 对应一个 dep;</li>
<li>2.2 通过 Object.getOwnPropertyDescriptor 方法获取 key 上的属性描述符</li>
<li>2.3 缓存 key 上的访问器函数和设置器函数</li>
<li>2.4 通过 Object.defineProperty 方法给 key 设置 get 和 set 方法（get 方法拦截对 obj[key]的访问, set 方法拦截对 obj[key]的新值设置</li>
<li>2.5 get 方法中通过 Dep 进行依赖收集</li>
<li>2.6 set 方法中设置新值, 并通过 Dep.notify 方法通知更新</li>
</ul>
</li>
<li><ol start="3">
<li>通过 proxy 方法将 Props[key]代理到 Vue 实例上, 支持通过 this.propsKey 的形式访问;</li>
</ol>
</li>
</ul>
<h4 id="initMethods-函数"><a href="#initMethods-函数" class="headerlink" title="initMethods 函数"></a>initMethods 函数</h4><p><code>目录:/src/core/instance/state.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">function initMethods(vm: Component, methods: Object) &#123;
    // 获取props对象
    const props = vm.$options.props
    // 遍历methods对象
    for (const key in methods) &#123;
        if (process.env.NODE_ENV !== 'production') &#123;
            if (typeof methods[key] !== 'function') &#123;
                warn(
                    `Method "$&#123;key&#125;" has type "$&#123;typeof methods[key]&#125;" in the component definition. ` +
                    `Did you reference the function correctly?`,
                    vm
                )
            &#125;
            // 将methods中的key与props对象中的key对比;做判重处理
            if (props && hasOwn(props, key)) &#123;
                warn(
                    `Method "$&#123;key&#125;" has already been defined as a prop.`,
                    vm
                )
            &#125;
            if ((key in vm) && isReserved(key)) &#123;
                warn(
                    `Method "$&#123;key&#125;" conflicts with an existing Vue instance method. ` +
                    `Avoid defining component methods that start with _ or $.`
                )
            &#125;
        &#125;
        // 将methods[key]代理到Vue实例上,支持通过this.methodsKey的形式访问;
        vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
    &#125;
&#125;</code></pre>
<p><strong>initMethods 函数做了那些事情？</strong></p>
<ul>
<li>1.0 获取 props 对象并缓存;</li>
<li>2.0 遍历 methods 对象中的 key;</li>
<li><ol start="3">
<li>将 methods 中的 key 与 props 对象中的 key 进行对比, 做判重处理;</li>
</ol>
</li>
<li><ol start="4">
<li>将 methods[key]代理到 Vue 实例上, 支持通过 this.methodsKey 的形式访问;</li>
</ol>
</li>
</ul>
<h4 id="initData-函数"><a href="#initData-函数" class="headerlink" title="initData 函数"></a>initData 函数</h4><p><code>目录:src/core/instance/state.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">function initData(vm: Component) &#123;
    // 缓存data对象
    let data = vm.$options.data
    data = vm._data = typeof data === 'function' ?
        getData(data, vm) :
        data || &#123;&#125;
    if (!isPlainObject(data)) &#123;
        data = &#123;&#125;
        process.env.NODE_ENV !== 'production' && warn(
            'data functions should return an object:\n' +
            'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
            vm
        )
    &#125;
    // 和props对象还有methods对象做判重处理
    // proxy data on instance
    const keys = Object.keys(data)
    const props = vm.$options.props
    const methods = vm.$options.methods
    let i = keys.length
    while (i--) &#123;
        const key = keys[i]
        if (process.env.NODE_ENV !== 'production') &#123;
            if (methods && hasOwn(methods, key)) &#123;
                warn(
                    `Method "$&#123;key&#125;" has already been defined as a data property.`,
                    vm
                )
            &#125;
        &#125;
        if (props && hasOwn(props, key)) &#123;
            process.env.NODE_ENV !== 'production' && warn(
                `The data property "$&#123;key&#125;" is already declared as a prop. ` +
                `Use prop default value instead.`,
                vm
            )
        &#125; else if (!isReserved(key)) &#123;
            // 将data代理到Vue实例上,支持通过this.dataKey的形式访问;
            proxy(vm, `_data`, key)
        &#125;
    &#125;
    // observe data
    observe(data, true /* asRootData */ )
&#125;</code></pre>
<p><strong>initData 函数做了那些事？</strong></p>
<ul>
<li>1.0 缓存 data 对象</li>
<li>2.0 data[key]与 methods[key]和 props[key]做对比, 判重处理;</li>
<li>3.0 将 data[key]代理到 Vue 实例上, 并支持 this.dataKey 的形式访问;</li>
</ul>
<h4 id="initComputed-函数"><a href="#initComputed-函数" class="headerlink" title="initComputed 函数"></a>initComputed 函数</h4><p><code>目录:/src/core/instance/state.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">function initComputed(vm: Component, computed: Object) &#123;
    // $flow-disable-line
    const watchers = (vm._computedWatchers = Object.create(null));
    // computed properties are just getters during SSR
    const isSSR = isServerRendering();
    // 遍历computed中的key,即getter函数
    for (const key in computed) &#123;
        const userDef = computed[key];
        const getter = typeof userDef === "function" ? userDef : userDef.get;
        if (process.env.NODE_ENV !== "production" && getter == null) &#123;
            warn(`Getter is missing for computed property "$&#123;key&#125;".`, vm);
        &#125;

        if (!isSSR) &#123;
            // create internal watcher for the computed property.
            // 创建一个watcher
            watchers[key] = new Watcher(
                vm,
                getter || noop,
                noop,
                // 配置项:computed默认懒执行
                computedWatcherOptions
            );
        &#125;

        // component-defined computed properties are already defined on the
        // component prototype. We only need to define computed properties defined
        // at instantiation here.
        if (!(key in vm)) &#123;
            // 代理computedKey到Vue实例上
            defineComputed(vm, key, userDef);
        &#125; else if (process.env.NODE_ENV !== "production") &#123;
            if (key in vm.$data) &#123;
                warn(`The computed property "$&#123;key&#125;" is already defined in data.`, vm);
            &#125; else if (vm.$options.props && key in vm.$options.props) &#123;
                warn(
                    `The computed property "$&#123;key&#125;" is already defined as a prop.`,
                    vm
                );
            &#125; else if (vm.$options.methods && key in vm.$options.methods) &#123;
                warn(
                    `The computed property "$&#123;key&#125;" is already defined as a method.`,
                    vm
                );
            &#125;
        &#125;
    &#125;
&#125;

// 代理computedKey到Vue实例上
export function defineComputed(
    target: any,
    key: string,
    userDef: Object | Function
) &#123;
    const shouldCache = !isServerRendering();
    if (typeof userDef === "function") &#123;
        sharedPropertyDefinition.get = shouldCache ?
            createComputedGetter(key) :
            createGetterInvoker(userDef);
        sharedPropertyDefinition.set = noop;
    &#125; else &#123;
        sharedPropertyDefinition.get = userDef.get ?
            shouldCache && userDef.cache !== false ?
            createComputedGetter(key) :
            createGetterInvoker(userDef.get) :
            noop;
        sharedPropertyDefinition.set = userDef.set || noop;
    &#125;
    if (
        process.env.NODE_ENV !== "production" &&
        sharedPropertyDefinition.set === noop
    ) &#123;
        sharedPropertyDefinition.set = function() &#123;
            warn(
                `Computed property "$&#123;key&#125;" was assigned to but it has no setter.`,
                this
            );
        &#125;;
    &#125;
    // 对computedKey做响应式处理
    Object.defineProperty(target, key, sharedPropertyDefinition);
&#125;</code></pre>
<p><strong>initComputed 函数做了那些事？</strong></p>
<ul>
<li>1.0 遍历 computed 对象中的 key, 即 getter 函数</li>
<li>2.0 创建一个 watcher 实例, 默认配置为懒执行;</li>
<li>3.0 通过 defineComputed 函数将 computedKey 代理到 Vue 实例上, 支持通过 this.computedKey 的形式访问;</li>
<li>4.0 通过 Object.defineProperty 方法对 computedKey 进行响应式处理;</li>
</ul>
<h4 id="initWatch-函数"><a href="#initWatch-函数" class="headerlink" title="initWatch 函数"></a>initWatch 函数</h4><p><code>目录:/src/core/instance/state.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">function initWatch(vm: Component, watch: Object) &#123;
    // 遍历watch对象
    for (const key in watch) &#123;
        const handler = watch[key];
        if (Array.isArray(handler)) &#123;
            // handler为数组,遍历数组,获取item,然后调用createWatcher函数
            for (let i = 0; i < handler.length; i++) &#123;
                createWatcher(vm, key, handler[i]);
            &#125;
        &#125; else &#123;
            createWatcher(vm, key, handler);
        &#125;
    &#125;
&#125;

function createWatcher(
    vm: Component,
    expOrFn: string | Function,
    handler: any,
    options ? : Object
) &#123;
    // 如果handler是对象,则获取handler选项的值;
    if (isPlainObject(handler)) &#123;
        options = handler;
        handler = handler.handler;
    &#125;
    // 如果是字符串,则说明是一个methods,获取handler
    if (typeof handler === "string") &#123;
        handler = vm[handler];
    &#125;
    return vm.$watch(expOrFn, handler, options);
&#125;

Vue.prototype.$watch = function(
    expOrFn: string | Function,
    cb: any,
    options ? : Object
): Function &#123;
    const vm: Component = this;
    if (isPlainObject(cb)) &#123;
        return createWatcher(vm, expOrFn, cb, options);
    &#125;
    options = options || &#123;&#125;;
    // 标记这是一个用户 watch
    options.user = true;
    // 创建一个watcher实例
    const watcher = new Watcher(vm, expOrFn, cb, options);
    // 如果有immediate属性,则立即执行回调函数;
    if (options.immediate) &#123;
        const info = `callback for immediate watcher "$&#123;watcher.expression&#125;"`;
        pushTarget();
        invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);
        popTarget();
    &#125;
    // 返回一个unwatch
    return function unwatchFn() &#123;
        watcher.teardown();
    &#125;;
&#125;;</code></pre>
<p><strong>initWatch 函数做了那些事情？</strong></p>
<ul>
<li>1.0 遍历 watch 对象, createWatcher 方法;</li>
<li>2.0 判断 watchKey 是对象还是字符串, 调用 vm.$watch 方法;</li>
<li>3.0 标记 watch 为用户 watcher;</li>
<li>4.0 创建 watcher 实例;</li>
<li>5.0 判断 immediate 属性, 如果有, 立即执行回调函数;</li>
<li>6.0 返回一个 unwatch;</li>
</ul>
<h4 id="Observe-函数"><a href="#Observe-函数" class="headerlink" title="Observe 函数"></a>Observe 函数</h4><p><code>目录:/src/core/observer/index.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">/**
 * 响应式处理的真正入口
 * 为对象创建观察者实例，如果对象已经被观察过，则返回已有的观察者实例，否则创建新的观察者实例
 * @param &#123;*&#125; value 对象 => &#123;&#125;
 */
export function observe(value: any, asRootData: ? boolean): Observer | void &#123;
    // 非对象和 VNode 实例不做响应式处理
    if (!isObject(value) || value instanceof VNode) &#123;
        return
    &#125;
    let ob: Observer | void
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) &#123;
        // 如果 value 对象上存在 __ob__ 属性，则表示已经做过观察了，直接返回 __ob__ 属性
        ob = value.__ob__
    &#125; else if (
        // 对数据对象进行检测
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
    ) &#123;
        // 创建观察者实例
        ob = new Observer(value)
    &#125;
    if (asRootData && ob) &#123;
        ob.vmCount++
    &#125;
    return ob
&#125;</code></pre>
<h4 id="Observer-函数"><a href="#Observer-函数" class="headerlink" title="Observer 函数"></a>Observer 函数</h4><p><code>目录:/src/core/observer/index.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">/**
 * 观察者类，会被附加到每个被观察的对象上，value.__ob__ = this
 * 而对象的各个属性则会被转换成 getter/setter，并收集依赖和通知更新
 */
export class Observer &#123;
    value: any;
    dep: Dep;
    vmCount: number; // number of vms that have this object as root $data

    constructor(value: any) &#123;
        this.value = value
        // 实例话一个 dep
        this.dep = new Dep()
        this.vmCount = 0
        // 在 value 对象上设置 __ob__ 属性
        def(value, '__ob__', this)
        if (Array.isArray(value)) &#123;
            /**
             * value 为数组
             * hasProto = '__proto__' in &#123;&#125;
             * 用于判断对象是否存在 __proto__ 属性，通过 obj.__proto__ 可以访问对象的原型链
             * 但由于 __proto__ 不是标准属性，所以有些浏览器不支持，比如 IE6-10，Opera10.1
             * 为什么要判断，是因为一会儿要通过 __proto__ 操作数据的原型链
             * 覆盖数组默认的七个原型方法，以实现数组响应式
             */
            if (hasProto) &#123;
                // 有 __proto__
                protoAugment(value, arrayMethods)
            &#125; else &#123;
                copyAugment(value, arrayMethods, arrayKeys)
            &#125;
            this.observeArray(value)
        &#125; else &#123;
            // value 为对象，为对象的每个属性（包括嵌套对象）设置响应式
            this.walk(value)
        &#125;
    &#125;

    /**
     * 遍历对象上的每个 key，为每个 key 设置响应式
     * 仅当值为对象时才会走这里
     */
    walk(obj: Object) &#123;
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) &#123;
            defineReactive(obj, keys[i])
        &#125;
    &#125;

    /**
     * 遍历数组，为数组的每一项设置观察，处理数组元素为对象的情况
     */
    observeArray(items: Array < any > ) &#123;
        for (let i = 0, l = items.length; i < l; i++) &#123;
            observe(items[i])
        &#125;
    &#125;
&#125;</code></pre>
<h4 id="Dep-函数"><a href="#Dep-函数" class="headerlink" title="Dep 函数"></a>Dep 函数</h4><p><code>目录:/src/core/observer/dep.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">import type Watcher from './watcher'
import &#123;
    remove
&#125; from '../util/index'
import config from '../config'

let uid = 0

/**
 * 一个 dep 对应一个 obj.key
 * 在读取响应式数据时，负责收集依赖，每个 dep（或者说 obj.key）依赖的 watcher 有哪些
 * 在响应式数据更新时，负责通知 dep 中那些 watcher 去执行 update 方法
 */
export default class Dep &#123;
    static target: ? Watcher;
    id: number;
    subs: Array < Watcher > ;

    constructor() &#123;
        this.id = uid++
        this.subs = []
    &#125;

    // 在 dep 中添加 watcher
    addSub(sub: Watcher) &#123;
        this.subs.push(sub)
    &#125;

    removeSub(sub: Watcher) &#123;
        remove(this.subs, sub)
    &#125;

    // 像 watcher 中添加 dep
    depend() &#123;
        if (Dep.target) &#123;
            Dep.target.addDep(this)
        &#125;
    &#125;

    /**
     * 通知 dep 中的所有 watcher，执行 watcher.update() 方法
     */
    notify() &#123;
        // stabilize the subscriber list first
        const subs = this.subs.slice()
        if (process.env.NODE_ENV !== 'production' && !config.async) &#123;
            // subs aren't sorted in scheduler if not running async
            // we need to sort them now to make sure they fire in correct
            // order
            subs.sort((a, b) => a.id - b.id)
        &#125;
        // 遍历 dep 中存储的 watcher，执行 watcher.update()
        for (let i = 0, l = subs.length; i < l; i++) &#123;
            subs[i].update()
        &#125;
    &#125;
&#125;

/**
 * 当前正在执行的 watcher，同一时间只会有一个 watcher 在执行
 * Dep.target = 当前正在执行的 watcher
 * 通过调用 pushTarget 方法完成赋值，调用 popTarget 方法完成重置（null)
 */
Dep.target = null
const targetStack = []

// 在需要进行依赖收集的时候调用，设置 Dep.target = watcher
export function pushTarget(target: ? Watcher) &#123;
    targetStack.push(target)
    Dep.target = target
&#125;

// 依赖收集结束调用，设置 Dep.target = null
export function popTarget() &#123;
    targetStack.pop()
    Dep.target = targetStack[targetStack.length - 1]
&#125;</code></pre>
<h4 id="watcher"><a href="#watcher" class="headerlink" title="watcher"></a>watcher</h4><p><code>目录:/src/core/observer/watcher.js</code></p>
<pre class=" language-javaScript"><code class="language-javaScript">/**
 * 一个组件一个 watcher（渲染 watcher）或者一个表达式一个 watcher（用户watcher）
 * 当数据更新时 watcher 会被触发，访问 this.computedProperty 时也会触发 watcher
 */
export default class Watcher &#123;
    vm: Component;
    expression: string;
    cb: Function;
    id: number;
    deep: boolean;
    user: boolean;
    lazy: boolean;
    sync: boolean;
    dirty: boolean;
    active: boolean;
    deps: Array < Dep > ;
    newDeps: Array < Dep > ;
    depIds: SimpleSet;
    newDepIds: SimpleSet;
    before: ? Function;
    getter: Function;
    value: any;

    constructor(
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options ? : ? Object,
        isRenderWatcher ? : boolean
    ) &#123;
        this.vm = vm
        if (isRenderWatcher) &#123;
            vm._watcher = this
        &#125;
        vm._watchers.push(this)
        // options
        if (options) &#123;
            this.deep = !!options.deep
            this.user = !!options.user
            this.lazy = !!options.lazy
            this.sync = !!options.sync
            this.before = options.before
        &#125; else &#123;
            this.deep = this.user = this.lazy = this.sync = false
        &#125;
        this.cb = cb
        this.id = ++uid // uid for batching
        this.active = true
        this.dirty = this.lazy // for lazy watchers
        this.deps = []
        this.newDeps = []
        this.depIds = new Set()
        this.newDepIds = new Set()
        this.expression = process.env.NODE_ENV !== 'production' ?
            expOrFn.toString() :
            ''
        // parse expression for getter
        if (typeof expOrFn === 'function') &#123;
            this.getter = expOrFn
        &#125; else &#123;
            // this.getter = function() &#123; return this.xx &#125;
            // 在 this.get 中执行 this.getter 时会触发依赖收集
            // 待后续 this.xx 更新时就会触发响应式
            this.getter = parsePath(expOrFn)
            if (!this.getter) &#123;
                this.getter = noop
                process.env.NODE_ENV !== 'production' && warn(
                    `Failed watching path: "$&#123;expOrFn&#125;" ` +
                    'Watcher only accepts simple dot-delimited paths. ' +
                    'For full control, use a function instead.',
                    vm
                )
            &#125;
        &#125;
        this.value = this.lazy ?
            undefined :
            this.get()
    &#125;

    /**
     * 执行 this.getter，并重新收集依赖
     * this.getter 是实例化 watcher 时传递的第二个参数，一个函数或者字符串，比如：updateComponent 或者 parsePath 返回的读取 this.xx 属性值的函数
     * 为什么要重新收集依赖？
     *   因为触发更新说明有响应式数据被更新了，但是被更新的数据虽然已经经过 observe 观察了，但是却没有进行依赖收集，
     *   所以，在更新页面时，会重新执行一次 render 函数，执行期间会触发读取操作，这时候进行依赖收集
     */
    get() &#123;
        // 打开 Dep.target，Dep.target = this
        pushTarget(this)
        // value 为回调函数执行的结果
        let value
        const vm = this.vm
        try &#123;
            // 执行回调函数，比如 updateComponent，进入 patch 阶段
            value = this.getter.call(vm, vm)
        &#125; catch (e) &#123;
            if (this.user) &#123;
                handleError(e, vm, `getter for watcher "$&#123;this.expression&#125;"`)
            &#125; else &#123;
                throw e
            &#125;
        &#125; finally &#123;
            // "touch" every property so they are all tracked as
            // dependencies for deep watching
            if (this.deep) &#123;
                traverse(value)
            &#125;
            // 关闭 Dep.target，Dep.target = null
            popTarget()
            this.cleanupDeps()
        &#125;
        return value
    &#125;

    /**
     * Add a dependency to this directive.
     * 两件事：
     *   1、添加 dep 给自己（watcher）
     *   2、添加自己（watcher）到 dep
     */
    addDep(dep: Dep) &#123;
        // 判重，如果 dep 已经存在则不重复添加
        const id = dep.id
        if (!this.newDepIds.has(id)) &#123;
            // 缓存 dep.id，用于判重
            this.newDepIds.add(id)
            // 添加 dep
            this.newDeps.push(dep)
            // 避免在 dep 中重复添加 watcher，this.depIds 的设置在 cleanupDeps 方法中
            if (!this.depIds.has(id)) &#123;
                // 添加 watcher 自己到 dep
                dep.addSub(this)
            &#125;
        &#125;
    &#125;

    /**
     * Clean up for dependency collection.
     */
    cleanupDeps() &#123;
        let i = this.deps.length
        while (i--) &#123;
            const dep = this.deps[i]
            if (!this.newDepIds.has(dep.id)) &#123;
                dep.removeSub(this)
            &#125;
        &#125;
        let tmp = this.depIds
        this.depIds = this.newDepIds
        this.newDepIds = tmp
        this.newDepIds.clear()
        tmp = this.deps
        this.deps = this.newDeps
        this.newDeps = tmp
        this.newDeps.length = 0
    &#125;

    /**
     * 根据 watcher 配置项，决定接下来怎么走，一般是 queueWatcher
     */
    update() &#123;
        /* istanbul ignore else */
        if (this.lazy) &#123;
            // 懒执行时走这里，比如 computed

            // 将 dirty 置为 true，可以让 computedGetter 执行时重新计算 computed 回调函数的执行结果
            this.dirty = true
        &#125; else if (this.sync) &#123;
            // 同步执行，在使用 vm.$watch 或者 watch 选项时可以传一个 sync 选项，
            // 当为 true 时在数据更新时该 watcher 就不走异步更新队列，直接执行 this.run
            // 方法进行更新
            // 这个属性在官方文档中没有出现
            this.run()
        &#125; else &#123;
            // 更新时一般都这里，将 watcher 放入 watcher 队列
            queueWatcher(this)
        &#125;
    &#125;

    /**
     * 由 刷新队列函数 flushSchedulerQueue 调用，完成如下几件事：
     *   1、执行实例化 watcher 传递的第二个参数，updateComponent 或者 获取 this.xx 的一个函数(parsePath 返回的函数)
     *   2、更新旧值为新值
     *   3、执行实例化 watcher 时传递的第三个参数，比如用户 watcher 的回调函数
     */
    run() &#123;
        if (this.active) &#123;
            // 调用 this.get 方法
            const value = this.get()
            if (
                value !== this.value ||
                // Deep watchers and watchers on Object/Arrays should fire even
                // when the value is the same, because the value may
                // have mutated.
                isObject(value) ||
                this.deep
            ) &#123;
                // 更新旧值为新值
                const oldValue = this.value
                this.value = value

                if (this.user) &#123;
                    // 如果是用户 watcher，则执行用户传递的第三个参数 —— 回调函数，参数为 val 和 oldVal
                    try &#123;
                        this.cb.call(this.vm, value, oldValue)
                    &#125; catch (e) &#123;
                        handleError(e, this.vm, `callback for watcher "$&#123;this.expression&#125;"`)
                    &#125;
                &#125; else &#123;
                    // 渲染 watcher，this.cb = noop，一个空函数
                    this.cb.call(this.vm, value, oldValue)
                &#125;
            &#125;
        &#125;
    &#125;

    /**
     * 懒执行的 watcher 会调用该方法
     *   比如：computed，在获取 vm.computedProperty 的值时会调用该方法
     * 然后执行 this.get，即 watcher 的回调函数，得到返回值
     * this.dirty 被置为 false，作用是页面在本次渲染中只会一次 computed.key 的回调函数，
     *   这也是大家常说的 computed 和 methods 区别之一是 computed 有缓存的原理所在
     * 而页面更新后会 this.dirty 会被重新置为 true，这一步是在 this.update 方法中完成的
     */
    evaluate() &#123;
        this.value = this.get()
        this.dirty = false
    &#125;

    /**
     * Depend on all deps collected by this watcher.
     */
    depend() &#123;
        let i = this.deps.length
        while (i--) &#123;
            this.deps[i].depend()
        &#125;
    &#125;

    /**
     * Remove self from all dependencies' subscriber list.
     */
    teardown() &#123;
        if (this.active) &#123;
            // remove self from vm's watcher list
            // this is a somewhat expensive operation so we skip it
            // if the vm is being destroyed.
            if (!this.vm._isBeingDestroyed) &#123;
                remove(this.vm._watchers, this)
            &#125;
            let i = this.deps.length
            while (i--) &#123;
                this.deps[i].removeSub(this)
            &#125;
            this.active = false
        &#125;
    &#125;
&#125;</code></pre>
<p>虽然看了 4 遍, 可是我还是没搞懂观察者 Observer/Dep/watcher 之间的关系, 真的很惨呀; 已经两天没工作; 全在看这个了; 这也太难了把;<br>加油~~</p>
<p>回答最初的两个问题</p>
<h4 id="watch-和-computed-和-methods-之间有设什么区别？"><a href="#watch-和-computed-和-methods-之间有设什么区别？" class="headerlink" title="watch 和 computed 和 methods 之间有设什么区别？"></a>watch 和 computed 和 methods 之间有设什么区别？</h4><p>watch 和 computed 原理都是通过对各自的对象进行遍历, 对 key 进行实例化一个 watcher, 然后调用 vm.$watcher 方法处理; 但是 computed 是懒执行的; watcher.dirty == false; 并且不可更改; 只有当依赖的值更新时, 触发 watcher.update 方法 watcher.dirty == true;<br>所以 watch 和 computed 区别如下：</p>
<ul>
<li>1.0 computed 是有缓存的, 只有当依赖值更新的时候, 才会重新计算;</li>
<li>2.0 使用场景不同, computed 只适合于一些简单的格式处理、同步运算; 但是 watch 一般用于数据变化时,执行异步操作;</li>
</ul>
<p>methods 也是,在一次渲染中,methods 可以被执行多次,但是 computed 只能执行一次;只有当依赖值更新的时候,computed 才会重新执行;</p>
<h4 id="Vue-数据的响应式原理"><a href="#Vue-数据的响应式原理" class="headerlink" title="Vue 数据的响应式原理?"></a>Vue 数据的响应式原理?</h4><p>我还是没懂弄 Oberser/watcher/Dep 三者之间的关系;心态崩了</p>
<p>2021.8.2 复习补充</p>
<h4 id="methods-和-computed-有什么区别？"><a href="#methods-和-computed-有什么区别？" class="headerlink" title="methods 和 computed 有什么区别？"></a>methods 和 computed 有什么区别？</h4><p>一次渲染中,只执行一次 computed 函数,后续的访问就不会再执行了,直到下一次的依赖更新;才会执行;<br><img src="https://i.loli.net/2021/08/02/BvExhUZ345bNorI.png" alt="computed和methods的区别.png"><br><img src="https://i.loli.net/2021/08/02/1NW7y6Ffdu2vB4j.png" alt="执行结果.png"><br>所以<code>methods 和 computed 有什么区别？</code><br>上述中,computed 只执行了一次;methods 执行了两次;</p>
<ul>
<li>在一次渲染中,只执行一次 computed 函数,后续的访问就不会再执行了,除非 computed 中的依赖更新了,才会重新执行;</li>
</ul>
<h4 id="computed-的实现原理？"><a href="#computed-的实现原理？" class="headerlink" title="computed 的实现原理？"></a>computed 的实现原理？</h4><p>computed 的初始化时在<code>initComputed方法中进行的</code>;</p>
<pre class=" language-javaScript"><code class="language-javaScript">function initComputed (vm, computed) &#123;
  // $flow-disable-line
  var watchers = vm._computedWatchers = Object.create(null);
  // computed properties are just getters during SSR
  var isSSR = isServerRendering();

  for (var key in computed) &#123;
    var userDef = computed[key];
    var getter = typeof userDef === 'function' ? userDef : userDef.get;
    if (process.env.NODE_ENV !== 'production' && getter == null) &#123;
      warn(
        ("Getter is missing for computed property \"" + key + "\"."),
        vm
      );
    &#125;

    if (!isSSR) &#123;
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
    &#125;

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) &#123;
      defineComputed(vm, key, userDef);
    &#125; else if (process.env.NODE_ENV !== 'production') &#123;
      if (key in vm.$data) &#123;
        warn(("The computed property \"" + key + "\" is already defined in data."), vm);
      &#125; else if (vm.$options.props && key in vm.$options.props) &#123;
        warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
      &#125;
    &#125;
  &#125;
&#125;</code></pre>
<p>initComputed 函数做了些什么？</p>
<ul>
<li>首先遍历 computed 中的 key;</li>
<li>给每一个 computed.key 实例化一个 watcher,并且第四个参数传入一个 computedOptions:{lazy:true};表示为懒执行;</li>
<li>将 computed.key 代理到 Vue 实例上,支持通过 this.key 的形式访问;</li>
</ul>
<h4 id="computed-的缓存的原理"><a href="#computed-的缓存的原理" class="headerlink" title="computed 的缓存的原理"></a>computed 的缓存的原理</h4><p>computed.key 在实例化为 watcher 的时候,多传递了一个参数 computedOptions:{lazy:true};<br>所以我们再去 watcher 中找一下 lazy 属性代表了什么？<br><code>目录:/src/core/observer/watcher.js</code><br><img src="https://i.loli.net/2021/08/02/7LZzM8lIAhTBVvx.png" alt="watcher中的dirty属性.png"><br>watcher 构造函数中,lazy 属性赋值给了 this.dirty;表示该 watcher 是懒执行;<br><img src="https://i.loli.net/2021/08/02/V2RGmB83yiFjp5S.png" alt="watcher-update.png"><br>当 watcher.dirty 表示为 true 的时候,watcher.update 方法中;只有当 watcher.dirty === false 时,才更新数据并加入到更新的 wahcer 队列中;<br>所以<strong>computed 中是有缓存</strong>的;</p>
<h4 id="watch-的实现原理"><a href="#watch-的实现原理" class="headerlink" title="watch 的实现原理"></a>watch 的实现原理</h4><pre class=" language-javaScript"><code class="language-javaScript">function initWatch(vm: Component, watch: Object) &#123;
  // 遍历watch对象
  for (const key in watch) &#123;
    const handler = watch[key];
    if (Array.isArray(handler)) &#123;
      // handler为数组,遍历数组,获取item,然后调用createWatcher函数
      for (let i = 0; i < handler.length; i++) &#123;
        createWatcher(vm, key, handler[i]);
      &#125;
    &#125; else &#123;
      createWatcher(vm, key, handler);
    &#125;
  &#125;
&#125;


function createWatcher(
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) &#123;
  // 如果handler是对象,则获取handler选项的值;
  if (isPlainObject(handler)) &#123;
    options = handler;
    handler = handler.handler;
  &#125;
  // 如果是字符串,则说明是一个methods,获取this.methods
  if (typeof handler === "string") &#123;
    handler = vm[handler];
  &#125;
  // 调用this.$watch方法
  return vm.$watch(expOrFn, handler, options);
&#125;


  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function &#123;
    const vm: Component = this;
    if (isPlainObject(cb)) &#123;
      return createWatcher(vm, expOrFn, cb, options);
    &#125;
    options = options || &#123;&#125;;
    // 标记这是一个用户 watch
    options.user = true;
    // 创建一个watcher实例
    const watcher = new Watcher(vm, expOrFn, cb, options);
    // 如果有immediate属性,则立即执行回调函数;
    if (options.immediate) &#123;
      const info = `callback for immediate watcher "$&#123;watcher.expression&#125;"`;
      pushTarget();
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);
      popTarget();
    &#125;
    // 返回一个unwatch
    return function unwatchFn() &#123;
      watcher.teardown();
    &#125;;
  &#125;;</code></pre>
<p>wach 初始化过程中做了什么？</p>
<ul>
<li>遍历 watch 中的 key;给每一个 key 实例化一个 watcher;</li>
</ul>
<h4 id="watch-和-computed-有什么区别？"><a href="#watch-和-computed-有什么区别？" class="headerlink" title="watch 和 computed 有什么区别？"></a>watch 和 computed 有什么区别？</h4><ul>
<li>computed 默认是懒执行的,所以他的值是有缓存的;且懒执行不可更改;watch 可配置;</li>
<li>使用场景不同,computed 一般用来进行一些简单的数据处理,和同步操作;而 watch 一般用来进行一些开销比较大的操作;并且同步操作和异步操作都可;</li>
</ul>
<h4 id="响应式数据处理"><a href="#响应式数据处理" class="headerlink" title="响应式数据处理"></a>响应式数据处理</h4><pre class=" language-javaScript"><code class="language-javaScript">// 响应式数据处理的入口
export function observe (value: any, asRootData: ?boolean): Observer | void &#123;
  if (!isObject(value) || value instanceof VNode) &#123;
    return
  &#125;
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) &#123;
    ob = value.__ob__
  &#125; else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) &#123;
    ob = new Observer(value)
  &#125;
  if (asRootData && ob) &#123;
    ob.vmCount++
  &#125;
  return ob
&#125;;

// Observer类
export class Observer &#123;
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) &#123;
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) &#123;
      if (hasProto) &#123;
        protoAugment(value, arrayMethods)
      &#125; else &#123;
        copyAugment(value, arrayMethods, arrayKeys)
      &#125;
      this.observeArray(value)
    &#125; else &#123;
      this.walk(value)
    &#125;
  &#125;

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) &#123;
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) &#123;
      defineReactive(obj, keys[i])
    &#125;
  &#125;

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) &#123;
    for (let i = 0, l = items.length; i < l; i++) &#123;
      observe(items[i])
    &#125;
  &#125;
&#125;;

// 响应式数据处理的核心
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) &#123;
  // 实例化一个Dep,一一对应的关系:一个Key一个Dep;
  const dep = new Dep()

  // MDN上解释：Object.getOwnPropertyDescriptor方法为返回指定对象上一个自有属性的属性描述符
  // （自有属性是指对对象的私有属性,不在原型链上进行查找）
  // 获取对象上的Key ====> property
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // MDN定义configurable：当切仅当指定对象的属性描述可以被改变或者属性可被删除时,为true
  // 发现属性描述符的configurable不可配置,直接return
  if (property && property.configurable === false) &#123;
    return
  &#125;

  // cater for pre-defined getter/setters
  // 获取属性描述符上访问器函数getter和设置气函数setter
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) &#123;
    val = obj[key]
  &#125;

  let childOb = !shallow && observe(val)
  // MDN描述Object.defineProperty:会直接在一个对象上定义新的属性,或者修改一个对象的现有属性,并返回该对象
  // 响应式数据处理的核心
  Object.defineProperty(obj, key, &#123;
    enumerable: true,
    configurable: true,
    // 设置get方法拦截obj[key]的访问
    get: function reactiveGetter () &#123;
      const value = getter ? getter.call(obj) : val
      // Dep.target是一个watcher
      if (Dep.target) &#123;
        // 依赖收集
        dep.depend()
        if (childOb) &#123;
          // obj[key]如果有子对象,循环调用依赖收集
          childOb.dep.depend()
          // 处理数组的依赖收集
          if (Array.isArray(value)) &#123;
            dependArray(value)
          &#125;
        &#125;
      &#125;
      return value
    &#125;,
    // 拦截对obj[key]读取操作
    set: function reactiveSetter (newVal) &#123;
      // 通过访问器函数获取——旧值
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      // 如果新值和旧值相等,直接return
      if (newVal === value || (newVal !== newVal && value !== value)) &#123;
        return
      &#125;
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) &#123;
        customSetter()
      &#125;
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      // 通过设置器函数设置新值
      if (setter) &#123;
        setter.call(obj, newVal)
      &#125; else &#123;
        val = newVal
      &#125;
      // observe为观察,让新值也是响应式的
      childOb = !shallow && observe(newVal)
      // 依赖通知更新
      dep.notify()
    &#125;
  &#125;)
&#125;</code></pre>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><ul>
<li><p>数据响应式的原理</p>
<ul>
<li>通过 Object.defineProperty 对 data 中的每一个 key 进行访问和设置新值的拦截处理:给每一个 key 设置 getter 和 setter;</li>
<li>访问数据时,对 key 进行依赖收集;在 dep 中存储相关的 watcher;</li>
<li>给数据设置新值时,调用 dep.notify()方法通知相关的 watcher 进行数据更新;</li>
</ul>
</li>
<li><p>watch 和 computed 的区别<br>watch 和 computed 原理都是通过对各自的对象进行遍历, 对 key 进行实例化一个 watcher, 然后调用 vm.$watcher 方法处理; 但是 computed 是懒执行的; watcher.dirty == false; 并且不可更改; 只有当依赖的值更新时, 触发 watcher.update 方法 watcher.dirty == true;<br>所以 watch 和 computed 区别如下：</p>
<ul>
<li>1.0 computed 是有缓存的, 只有当依赖值更新的时候, 才会重新计算;</li>
<li>2.0 使用场景不同, computed 只适合于一些简单的格式处理、同步运算; 但是 watch 一般用于数据变化时,执行异步操作;</li>
</ul>
</li>
</ul>
<p>methods 也是,在一次渲染中,methods 可以被执行多次,但是 computed 只能执行一次;只有当依赖值更新的时候,computed 才会重新执行;</p>
<h4 id="2022-06-22复习"><a href="#2022-06-22复习" class="headerlink" title="2022.06.22复习"></a>2022.06.22复习</h4><p>双向式数据绑定的原理？<br>Object.defineProperty方法对对象中的key进行数据读取和设置的拦截,通过给get和set的方法对key的读取和设置进行拦截</p>
<ol>
<li>当触发get方法时,读取key,触发Dep(依赖管理).addDep进行依赖收集</li>
<li>当触发set方法时,获取新值,触发Dep(依赖管理).notify()方法通知依赖更新,让watcher执行update()方法,进入异步更新阶段</li>
</ol>
<p>数组:<br>通过复写(增强)原型上的push pop shift unshift sort reverse splice七个方法让具有依赖通知更新的能力,以达到数组响应式的能力</p>
<ol>
<li>当添加新数据时,进行响应式处理,然后由dep通知watcher去执行update方法,进入异步更新阶段</li>
<li>当删除新数据时,也是由dep通知watcher去执行update方法,进入异步更新阶段</li>
</ol>
<p>什么是Dep？<br>比如data中有一个变量为messsage,当他要渲染到template中,这就是一个依赖;可以理解为通知数据要更新到哪里去<br>computed中有一个messageT通过message处理了,这是第二个依赖</p>
<p>插槽<br>定义:当我们在子组件的插入对应的子节点时,需要渲染子组件中;就是插槽<br><Child>插槽的具体内容</Child></p>
<p>子组件</p>
<div>
<slot></slot>
</div>

<p>slot标签中渲染的就是插槽的具体内容<br>插槽中可以写html,也可以写子组件</p>
<p>当有多个插槽,需要我们区分时,可以给插槽加上name属性,我们称之为具名插槽</p>
<p>作用域插槽<br>当子组件中的插槽需要用到父组件中的属性时,可以通过v-slot:user = “user”的方式进行传参,我们称之为作用域插槽</p>

                
            </div>
            <hr/>

            



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/pride-blog/tags/Vue%E6%BA%90%E7%A0%81/">
                                    <span class="chip bg-color">Vue源码</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/pride-blog/libs/share/css/share.min.css">
<div id="article-share">

    

    

</div>

                </div>
            </div>
            
        </div>
    </div>

    

    

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/pride-blog/2021/061142501.html">
                    <div class="card-image">
                        
                        
                        <img src="/pride-blog/medias/featureimages/4.jpg" class="responsive-img" alt="Vuex的单一状态管理树">
                        
                        <span class="card-title">Vuex的单一状态管理树</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2021-06-11
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/pride-blog/categories/%E6%AF%8F%E5%A4%A9%E4%B8%80%E4%B8%AA%E7%A6%BB%E8%81%8C%E5%B0%8F%E6%8A%80%E5%B7%A7/" class="post-category">
                                    每天一个离职小技巧
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/pride-blog/tags/log/">
                        <span class="chip bg-color">log</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/pride-blog/2021/060944106.html">
                    <div class="card-image">
                        
                        
                        <img src="/pride-blog/medias/featureimages/14.jpg" class="responsive-img" alt="Vue源码系列1-new Vue过程中发生了什么">
                        
                        <span class="card-title">Vue源码系列1-new Vue过程中发生了什么</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2021-06-09
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/pride-blog/categories/Vue%E6%BA%90%E7%A0%81/" class="post-category">
                                    Vue源码
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/pride-blog/tags/Vue%E6%BA%90%E7%A0%81/">
                        <span class="chip bg-color">Vue源码</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>



<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/pride-blog/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/pride-blog/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="/pride-blog/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/pride-blog/libs/codeBlock/codeShrink.js"></script>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card" style="background-color: white;">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/pride-blog/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h2, h3, h4'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h2, h3, h4').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    

    <div class="container row center-align"
         style="margin-bottom: 0px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
                <span id="year">2021-2023</span>
            
            <span id="year">2021</span>
            <a href="/pride-blog/about" target="_blank">zihao xiong</a>
            |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>
            
            
            
            
            
            <br>

            <!-- 运行天数提醒. -->
            
            <br>
            
        </div>
        <div class="col s12 m4 l4 social-link ">














</div>
    </div>
</footer>

<div class="progress-bar"></div>


    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/pride-blog/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="/pride-blog/libs/materialize/materialize.min.js"></script>
    <script src="/pride-blog/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/pride-blog/libs/aos/aos.js"></script>
    <script src="/pride-blog/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/pride-blog/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/pride-blog/js/matery.js"></script>

    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    <script src="/pride-blog/libs/others/clicklove.js" async="async"></script>
    
    

    

    

    <!--腾讯兔小巢-->
    
    
    <script type="text/javascript" color="0,0,255"
        pointColor="0,0,255" opacity='0.7'
        zIndex="-1" count="99"
        src="/pride-blog/libs/background/canvas-nest.js"></script>
    

    
    
    <script type="text/javascript" size="150" alpha='0.6'
        zIndex="-1" src="/pride-blog/libs/background/ribbon-refresh.min.js" async="async"></script>
    

    
    <script type="text/javascript" src="/pride-blog/libs/background/ribbon-dynamic.js" async="async"></script>
    

    
    <script src="/pride-blog/libs/instantpage/instantpage.js" type="module"></script><!-- hexo-inject:begin --><!-- hexo-inject:end -->
    

</body>
<script src="./../source/js/cursor.js"></script>

</html>
